/*
 * Pixel Dungeon
 * Copyright (C) 2012-2015 Oleg Dolya
 *
 * Shattered Pixel Dungeon
 * Copyright (C) 2014-2024 Evan Debenham
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

package com.shatteredpixel.shatteredpixeldungeon.utils;

import com.shatteredpixel.shatteredpixeldungeon.Badges;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.huntress.SpiritHawk;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.ShadowClone;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.abilities.rogue.SmokeBomb;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Acidic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Albino;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.ArmoredBrute;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.ArmoredStatue;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bandit;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bat;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Bee;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Brute;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.CausticSlime;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Crab;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.CrystalMimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Cyclops;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM100;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM200;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM201;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DM300;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DemonSpawner;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Dragun;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.DwarfKing;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Elemental;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Eye;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.FetidRat;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Ghoul;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Gnoll;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GnollTrickster;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GoldenMimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Golem;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Golyat;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Goo;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.GreatCrab;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Guard;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Hydra;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Jupiter;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mg5;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Mimic;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Monk;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Necromancer;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Nemeum;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Piranha;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Rat;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.RipperDemon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.RotHeart;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.RotLasher;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Scorpio;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Senior;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Shaman;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Skeleton;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Snake;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.SpectralNecromancer;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Spinner;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Statue;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Succubus;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Swarm;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Tengu;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Thief;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Typhoon;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Warlock;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.Wraith;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.YogDzewa;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.YogFist;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Blacksmith;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Ghost;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Imp;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.MirrorImage;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.PrismaticImage;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.RatKing;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Sheep;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Shopkeeper;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Wandmaker;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.CorpseDust;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfLivingEarth;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfRegrowth;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfWarding;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.SentryRoom;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.AlarmTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.BlazingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.BurningTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ChillingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ConfusionTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.CorrosionTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.CursingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.DisarmingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.DisintegrationTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.DistortionTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ExplosiveTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.FlashingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.FlockTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.FrostTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.GatewayTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.GeyserTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.GrimTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.GrippingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.GuardianTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.OozeTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.PoisonDartTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.RockfallTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ShockingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.StormTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.SummoningTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.TeleportationTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.TenguDartTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.ToxicTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.WarpingTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.WeakeningTrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.traps.WornDartTrap;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.plants.BlandfruitBush;
import com.shatteredpixel.shatteredpixeldungeon.plants.Blindweed;
import com.shatteredpixel.shatteredpixeldungeon.plants.Dreamfoil;
import com.shatteredpixel.shatteredpixeldungeon.plants.Earthroot;
import com.shatteredpixel.shatteredpixeldungeon.plants.Fadeleaf;
import com.shatteredpixel.shatteredpixeldungeon.plants.Firebloom;
import com.shatteredpixel.shatteredpixeldungeon.plants.Icecap;
import com.shatteredpixel.shatteredpixeldungeon.plants.Rotberry;
import com.shatteredpixel.shatteredpixeldungeon.plants.Sorrowmoss;
import com.shatteredpixel.shatteredpixeldungeon.plants.Starflower;
import com.shatteredpixel.shatteredpixeldungeon.plants.Stormvine;
import com.shatteredpixel.shatteredpixeldungeon.plants.Sungrass;
import com.shatteredpixel.shatteredpixeldungeon.plants.Swiftthistle;
import com.watabou.utils.Bundle;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;

//contains all the game's various entities, mostly enemies, NPCS, and allies, but also traps and plants
public enum MobBestiary {

    SEWER,
    PRISON,
    CAVES,
    CITYS,
    RECAVES,
    HALLS,

    UNIVERSAL,
    RARE,
    QUEST,

    NEUTRAL,

    ALLY,
    TRAP,
    PLANT;

    //tracks whether an entity has been encountered
    private final LinkedHashMap<Class<?>, Boolean> seen = new LinkedHashMap<>();
    //tracks enemy kills, trap activations, plant tramples, or just sets to 1 for seen on allies
    private final LinkedHashMap<Class<?>, Integer> encounterCount = new LinkedHashMap<>();

    //should only be used when initializing
    private void addEntities(Class<?>... classes ){
        for (Class<?> cls : classes){
            seen.put(cls, false);
            encounterCount.put(cls, 0);
        }
    }

    public Collection<Class<?>> entities(){
        return seen.keySet();
    }

    public String title(){
        return Messages.get(this, name() + ".title");
    }

    public int totalEntities(){
        return seen.size();
    }

    public int totalSeen(){
        int seenTotal = 0;
        for (boolean entitySeen : seen.values()){
            if (entitySeen) seenTotal++;
        }
        return seenTotal;
    }

    static {

        SEWER.addEntities(
                Snake.class, Rat.class, Gnoll.class, Crab.class, Swarm.class);

        PRISON.addEntities(Golyat.class,Thief.class,
                Shaman.RedShaman.class,
                Shaman.BlueShaman.class,
                Shaman.PurpleShaman.class,
                Mg5.class,Elemental.NewbornFireElemental.class);

        CAVES.addEntities(Bat.class,Brute.class,
                Spinner.class,Nemeum.class);

        CITYS.addEntities(Elemental.FireElemental.class,Elemental.FrostElemental.class,Elemental.ChaosElemental.class,
                Ghoul.class,Monk.class,Warlock.class,Golem.class);

        RECAVES.addEntities(
                Dragun.class,Guard.class,Jupiter.class
        );

        HALLS.addEntities(Cyclops.class,Succubus.class,
        Jupiter.class, Hydra.class, Typhoon.class);


        UNIVERSAL.addEntities(Wraith.class, Piranha.class, Mimic.class, GoldenMimic.class,Statue.class, GuardianTrap.Guardian.class, SentryRoom.Sentry.class);

        RARE.addEntities(
                Albino.class, CausticSlime.class,
                Bandit.class,
                ArmoredBrute.class,
                Elemental.ChaosElemental.class, Senior.class,
                CrystalMimic.class, ArmoredStatue.class);

        QUEST.addEntities(
                FetidRat.class, GnollTrickster.class, GreatCrab.class,
                Elemental.NewbornFireElemental.class, RotLasher.class, RotHeart.class);

        NEUTRAL.addEntities(
                Ghost.class, RatKing.class, Shopkeeper.class,
                Wandmaker.class, Blacksmith.class, Imp.class,
                Sheep.class, Bee.class);

        ALLY.addEntities(MirrorImage.class, PrismaticImage.class,
                DriedRose.GhostHero.class,
                WandOfWarding.Ward.class,
                WandOfLivingEarth.EarthGuardian.class,
                ShadowClone.ShadowAlly.class, SmokeBomb.NinjaLog.class, SpiritHawk.HawkAlly.class);

        TRAP.addEntities(WornDartTrap.class, PoisonDartTrap.class, DisintegrationTrap.class, GatewayTrap.class,
                ChillingTrap.class, BurningTrap.class, ShockingTrap.class, AlarmTrap.class, GrippingTrap.class, TeleportationTrap.class, OozeTrap.class,
                FrostTrap.class, BlazingTrap.class, StormTrap.class, GuardianTrap.class, FlashingTrap.class, WarpingTrap.class,
                ConfusionTrap.class, ToxicTrap.class, CorrosionTrap.class,
                FlockTrap.class, SummoningTrap.class, WeakeningTrap.class, CursingTrap.class,
                GeyserTrap.class, ExplosiveTrap.class, RockfallTrap.class,
                DistortionTrap.class, DisarmingTrap.class, GrimTrap.class);

        PLANT.addEntities(Rotberry.class, Sungrass.class, Fadeleaf.class, Icecap.class,
                Firebloom.class, Sorrowmoss.class, Swiftthistle.class, Blindweed.class,
                Stormvine.class, Earthroot.class, Dreamfoil.class, Starflower.class,
                BlandfruitBush.class,
                WandOfRegrowth.Dewcatcher.class, WandOfRegrowth.Seedpod.class, WandOfRegrowth.Lotus.class);

    }

    //some mobs and traps have different internal classes in some cases, so need to convert here
    private static final HashMap<Class<?>, Class<?>> classConversions = new HashMap<>();
    static {
        classConversions.put(CorpseDust.DustWraith.class,      Wraith.class);

        classConversions.put(Necromancer.NecroSkeleton.class,  Skeleton.class);

        classConversions.put(TenguDartTrap.class,              PoisonDartTrap.class);

        classConversions.put(DwarfKing.DKGhoul.class,          Ghoul.class);
        classConversions.put(DwarfKing.DKWarlock.class,        Warlock.class);
        classConversions.put(DwarfKing.DKMonk.class,           Monk.class);
        classConversions.put(DwarfKing.DKGolem.class,          Golem.class);

        classConversions.put(YogDzewa.YogRipper.class,         RipperDemon.class);
        classConversions.put(YogDzewa.YogEye.class,            Eye.class);
        classConversions.put(YogDzewa.YogScorpio.class,        Scorpio.class);
    }

    public static boolean isSeen(Class<?> cls){
        for (MobBestiary cat : values()) {
            if (cat.seen.containsKey(cls)) {
                return cat.seen.get(cls);
            }
        }
        return false;
    }

    public static void setSeen(Class<?> cls){
        if (classConversions.containsKey(cls)){
            cls = classConversions.get(cls);
        }
        for (MobBestiary cat : values()) {
            if (cat.seen.containsKey(cls) && !cat.seen.get(cls)) {
                cat.seen.put(cls, true);
                //TODO 预载破碎2.5.0图鉴系统
                //Journal.saveNeeded = true;
            }
        }
        //TODO 预载破碎2.5.0图鉴系统
        //Badges.validateCatalogBadges();
    }

    public static int encounterCount(Class<?> cls) {
        for (MobBestiary cat : values()) {
            if (cat.encounterCount.containsKey(cls)) {
                return cat.encounterCount.get(cls);
            }
        }
        return 0;
    }

    //used primarily when bosses are killed and need to clean up their minions
    public static boolean skipCountingEncounters = false;

    public static void countEncounter(Class<?> cls){
        countEncounters(cls, 1);
    }

    public static void countEncounters(Class<?> cls, int encounters){
        if (skipCountingEncounters){
            return;
        }
        if (classConversions.containsKey(cls)){
            cls = classConversions.get(cls);
        }
        for (MobBestiary cat : values()) {
            if (cat.encounterCount.containsKey(cls) && cat.encounterCount.get(cls) != Integer.MAX_VALUE){
                cat.encounterCount.put(cls, cat.encounterCount.get(cls)+encounters);
                if (cat.encounterCount.get(cls) < -1_000_000_000){ //to catch cases of overflow
                    cat.encounterCount.put(cls, Integer.MAX_VALUE);
                }
                //TODO 预载破碎2.5.0图鉴系统
                //Journal.saveNeeded = true;
            }
        }
    }

    private static final String MobBestiary_CLASSES    = "MobBestiary_classes";
    private static final String MobBestiary_SEEN       = "MobBestiary_seen";
    private static final String MobBestiary_ENCOUNTERS = "MobBestiary_encounters";

    public static void store( Bundle bundle ){

        ArrayList<Class<?>> classes = new ArrayList<>();
        ArrayList<Boolean> seen = new ArrayList<>();
        ArrayList<Integer> encounters = new ArrayList<>();

        for (MobBestiary cat : values()) {
            for (Class<?> entity : cat.entities()) {
                if (cat.seen.get(entity) || cat.encounterCount.get(entity) > 0){
                    classes.add(entity);
                    seen.add(cat.seen.get(entity));
                    encounters.add(cat.encounterCount.get(entity));
                }
            }
        }

        Class<?>[] storeCls = new Class[classes.size()];
        boolean[] storeSeen = new boolean[seen.size()];
        int[] storeEncounters = new int[encounters.size()];

        for (int i = 0; i < storeCls.length; i++){
            storeCls[i] = classes.get(i);
            storeSeen[i] = seen.get(i);
            storeEncounters[i] = encounters.get(i);
        }

        bundle.put( MobBestiary_CLASSES, storeCls );
        bundle.put( MobBestiary_SEEN, storeSeen );
        bundle.put( MobBestiary_ENCOUNTERS, storeEncounters );

    }

    public static void restore( Bundle bundle ){

        if (bundle.contains(MobBestiary_CLASSES)
                && bundle.contains(MobBestiary_SEEN)
                && bundle.contains(MobBestiary_ENCOUNTERS)){
            Class<?>[] classes = bundle.getClassArray(MobBestiary_CLASSES);
            boolean[] seen = bundle.getBooleanArray(MobBestiary_SEEN);
            int[] encounters = bundle.getIntArray(MobBestiary_ENCOUNTERS);

            for (int i = 0; i < classes.length; i++){
                for (MobBestiary cat : values()){
                    if (cat.seen.containsKey(classes[i])){
                        cat.seen.put(classes[i], seen[i]);
                        cat.encounterCount.put(classes[i], encounters[i]);
                    }
                }
            }
        }

    }

}
